// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_

#import <Cocoa/Cocoa.h>
#include <IOSurface/IOSurface.h>
#include <stddef.h>
#include <stdint.h>

#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "cc/surfaces/surface_id.h"
#include "content/browser/renderer_host/browser_compositor_view_mac.h"
#include "content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/text_input_manager.h"
#include "content/common/content_export.h"
#include "content/common/cursors/webcursor.h"
#include "content/common/edit_command.h"
#include "ipc/ipc_sender.h"
#include "third_party/WebKit/public/web/WebCompositionUnderline.h"
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
#include "ui/accelerated_widget_mac/display_link_mac.h"
#import "ui/base/cocoa/command_dispatcher.h"
#include "ui/base/cocoa/remote_layer_api.h"
#import "ui/base/cocoa/tool_tip_base_view.h"
#include "ui/display/display_observer.h"

namespace content {
class RenderWidgetHost;
class RenderWidgetHostImpl;
class RenderWidgetHostViewMac;
class RenderWidgetHostViewMacEditCommandHelper;
class WebContents;
struct TextInputState;
}

@class FullscreenWindowManager;
@protocol RenderWidgetHostViewMacDelegate;

@protocol RenderWidgetHostViewMacOwner
- (content::RenderWidgetHostViewMac*)renderWidgetHostViewMac;
@end

// This is the view that lives in the Cocoa view hierarchy. In Windows-land,
// RenderWidgetHostViewWin is both the view and the delegate. We split the roles
// but that means that the view needs to own the delegate and will dispose of it
// when it's removed from the view system.
@interface RenderWidgetHostViewCocoa
    : ToolTipBaseView <CommandDispatcherTarget,
          RenderWidgetHostViewMacOwner,
          NSTextInputClient> {
@private
    std::unique_ptr<content::RenderWidgetHostViewMac> renderWidgetHostView_;
    // This ivar is the cocoa delegate of the NSResponder.
    base::scoped_nsobject<NSObject<RenderWidgetHostViewMacDelegate>>
        responderDelegate_;
    BOOL canBeKeyView_;
    BOOL closeOnDeactivate_;
    BOOL opaque_;
    std::unique_ptr<content::RenderWidgetHostViewMacEditCommandHelper>
        editCommand_helper_;

    // Is YES if there was a mouse-down as yet unbalanced with a mouse-up.
    BOOL hasOpenMouseDown_;

    // The cursor for the page. This is passed up from the renderer.
    base::scoped_nsobject<NSCursor> currentCursor_;

    // Variables used by our implementaion of the NSTextInput protocol.
    // An input method of Mac calls the methods of this protocol not only to
    // notify an application of its status, but also to retrieve the status of
    // the application. That is, an application cannot control an input method
    // directly.
    // This object keeps the status of a composition of the renderer and returns
    // it when an input method asks for it.
    // We need to implement Objective-C methods for the NSTextInput protocol. On
    // the other hand, we need to implement a C++ method for an IPC-message
    // handler which receives input-method events from the renderer.

    // Represents the input-method attributes supported by this object.
    base::scoped_nsobject<NSArray> validAttributesForMarkedText_;

    // Indicates if we are currently handling a key down event.
    BOOL handlingKeyDown_;

    // Indicates if there is any marked text.
    BOOL hasMarkedText_;

    // Indicates if unmarkText is called or not when handling a keyboard
    // event.
    BOOL unmarkTextCalled_;

    // The range of current marked text inside the whole content of the DOM node
    // being edited.
    // TODO(suzhe): This is currently a fake value, as we do not support accessing
    // the whole content yet.
    NSRange markedRange_;

    // The selected range, cached from a message sent by the renderer.
    NSRange selectedRange_;

    // Text to be inserted which was generated by handling a key down event.
    base::string16 textToBeInserted_;

    // Marked text which was generated by handling a key down event.
    base::string16 markedText_;

    // Selected range of |markedText_|.
    NSRange markedTextSelectedRange_;

    // Underline information of the |markedText_|.
    std::vector<blink::WebCompositionUnderline> underlines_;

    // Replacement range information received from |setMarkedText:|.
    gfx::Range setMarkedTextReplacementRange_;

    // Indicates if doCommandBySelector method receives any edit command when
    // handling a key down event.
    BOOL hasEditCommands_;

    // Contains edit commands received by the -doCommandBySelector: method when
    // handling a key down event, not including inserting commands, eg. insertTab,
    // etc.
    content::EditCommands editCommands_;

    // Whether the previous mouse event was ignored due to hitTest check.
    BOOL mouseEventWasIgnored_;

    // Event monitor for scroll wheel end event.
    id endWheelMonitor_;

    // When a gesture starts, the system does not inform the view of which type
    // of gesture is happening (magnify, rotate, etc), rather, it just informs
    // the view that some as-yet-undefined gesture is starting. Capture the
    // information about the gesture's beginning event here. It will be used to
    // create a specific gesture begin event later.
    std::unique_ptr<blink::WebGestureEvent> gestureBeginEvent_;

    // To avoid accidental pinches, require that a certain zoom threshold be
    // reached before forwarding it to the browser. Use |pinchUnusedAmount_| to
    // hold this value. If the user reaches this value, don't re-require the
    // threshold be reached until the page has been zoomed back to page scale of
    // one.
    bool pinchHasReachedZoomThreshold_;
    float pinchUnusedAmount_;
    NSTimeInterval pinchLastGestureTimestamp_;

    // This is set if a GesturePinchBegin event has been sent in the lifetime of
    // |gestureBeginEvent_|. If set, a GesturePinchEnd will be sent when the
    // gesture ends.
    BOOL gestureBeginPinchSent_;

    // If true then escape key down events are suppressed until the first escape
    // key up event. (The up event is suppressed as well). This is used by the
    // flash fullscreen code to avoid sending a key up event without a matching
    // key down event.
    BOOL suppressNextEscapeKeyUp_;

    // The set of key codes from key down events that we haven't seen the matching
    // key up events yet.
    // Used for filtering out non-matching NSKeyUp events.
    std::set<unsigned short> keyDownCodes_;

    // The filter used to guide touch events towards a horizontal or vertical
    // orientation.
    content::MouseWheelRailsFilterMac mouseWheelFilter_;
}

@property (nonatomic, readonly) NSRange selectedRange;
@property (nonatomic, readonly) BOOL suppressNextEscapeKeyUp;

- (void)setCanBeKeyView:(BOOL)can;
- (void)setCloseOnDeactivate:(BOOL)b;
- (void)setOpaque:(BOOL)opaque;
// True for always-on-top special windows (e.g. Balloons and Panels).
- (BOOL)acceptsMouseEventsWhenInactive;
// Cancel ongoing composition (abandon the marked text).
- (void)cancelComposition;
// Confirm ongoing composition.
- (void)finishComposingText;
- (void)updateCursor:(NSCursor*)cursor;
- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
                             actualRange:(NSRangePointer)actualRange;
- (void)quickLookWithEvent:(NSEvent*)event;
- (void)showLookUpDictionaryOverlayAtPoint:(NSPoint)point;
- (void)showLookUpDictionaryOverlayFromRange:(NSRange)range
                                  targetView:(NSView*)targetView;
@end

namespace content {

///////////////////////////////////////////////////////////////////////////////
// RenderWidgetHostViewMac
//
//  An object representing the "View" of a rendered web page. This object is
//  responsible for displaying the content of the web page, and integrating with
//  the Cocoa view system. It is the implementation of the RenderWidgetHostView
//  that the cross-platform RenderWidgetHost object uses
//  to display the data.
//
//  Comment excerpted from render_widget_host.h:
//
//    "The lifetime of the RenderWidgetHost* is tied to the render process.
//     If the render process dies, the RenderWidgetHost* goes away and all
//     references to it must become NULL."
//
// RenderWidgetHostView class hierarchy described in render_widget_host_view.h.
class CONTENT_EXPORT RenderWidgetHostViewMac
    : public RenderWidgetHostViewBase,
      public BrowserCompositorMacClient,
      public TextInputManager::Observer,
      public ui::AcceleratedWidgetMacNSView,
      public IPC::Sender,
      public display::DisplayObserver {
public:
    // The view will associate itself with the given widget. The native view must
    // be hooked up immediately to the view hierarchy, or else when it is
    // deleted it will delete this out from under the caller.
    //
    // When |is_guest_view_hack| is true, this view isn't really the view for
    // the |widget|, a RenderWidgetHostViewGuest is.
    // TODO(lazyboy): Remove |is_guest_view_hack| once BrowserPlugin has migrated
    // to use RWHVChildFrame (http://crbug.com/330264).
    RenderWidgetHostViewMac(RenderWidgetHost* widget, bool is_guest_view_hack);
    ~RenderWidgetHostViewMac() override;

    RenderWidgetHostViewCocoa* cocoa_view() const { return cocoa_view_; }

    // |delegate| is used to separate out the logic from the NSResponder delegate.
    // |delegate| is retained by this class.
    // |delegate| should be set at most once.
    CONTENT_EXPORT void SetDelegate(
        NSObject<RenderWidgetHostViewMacDelegate>* delegate);
    void SetAllowPauseForResizeOrRepaint(bool allow);

    // RenderWidgetHostView implementation.
    bool OnMessageReceived(const IPC::Message& msg) override;
    void InitAsChild(gfx::NativeView parent_view) override;
    RenderWidgetHost* GetRenderWidgetHost() const override;
    void SetSize(const gfx::Size& size) override;
    void SetBounds(const gfx::Rect& rect) override;
    gfx::Vector2dF GetLastScrollOffset() const override;
    gfx::NativeView GetNativeView() const override;
    gfx::NativeViewAccessible GetNativeViewAccessible() override;
    bool HasFocus() const override;
    bool IsSurfaceAvailableForCopy() const override;
    void Show() override;
    void Hide() override;
    bool IsShowing() override;
    void WasUnOccluded() override;
    void WasOccluded() override;
    gfx::Rect GetViewBounds() const override;
    void SetShowingContextMenu(bool showing) override;
    void SetActive(bool active) override;
    void ShowDefinitionForSelection() override;
    bool SupportsSpeech() const override;
    void SpeakSelection() override;
    bool IsSpeaking() const override;
    void StopSpeaking() override;
    void SetBackgroundColor(SkColor color) override;
    void SetNeedsBeginFrames(bool needs_begin_frames) override;

    // Implementation of RenderWidgetHostViewBase.
    void InitAsPopup(RenderWidgetHostView* parent_host_view,
        const gfx::Rect& pos) override;
    void InitAsFullscreen(RenderWidgetHostView* reference_host_view) override;
    void Focus() override;
    void UpdateCursor(const WebCursor& cursor) override;
    void SetIsLoading(bool is_loading) override;
    void RenderProcessGone(base::TerminationStatus status,
        int error_code) override;
    void Destroy() override;
    void SetTooltipText(const base::string16& tooltip_text) override;
    void CopyFromCompositingSurface(const gfx::Rect& src_subrect,
        const gfx::Size& dst_size,
        const ReadbackRequestCallback& callback,
        SkColorType preferred_color_type) override;
    void CopyFromCompositingSurfaceToVideoFrame(
        const gfx::Rect& src_subrect,
        const scoped_refptr<media::VideoFrame>& target,
        const base::Callback<void(const gfx::Rect&, bool)>& callback) override;
    bool CanCopyToVideoFrame() const override;
    void BeginFrameSubscription(
        std::unique_ptr<RenderWidgetHostViewFrameSubscriber> subscriber) override;
    void EndFrameSubscription() override;
    ui::AcceleratedWidgetMac* GetAcceleratedWidgetMac() const override;
    void FocusedNodeChanged(bool is_editable_node,
        const gfx::Rect& node_bounds_in_screen) override;
    void OnSwapCompositorFrame(uint32_t compositor_frame_sink_id,
        cc::CompositorFrame frame) override;
    void ClearCompositorFrame() override;
    BrowserAccessibilityManager* CreateBrowserAccessibilityManager(
        BrowserAccessibilityDelegate* delegate, bool for_root_frame) override;
    gfx::Point AccessibilityOriginInScreen(const gfx::Rect& bounds) override;
    gfx::AcceleratedWidget AccessibilityGetAcceleratedWidget() override;

    bool HasAcceleratedSurface(const gfx::Size& desired_size) override;
    gfx::Rect GetBoundsInRootWindow() override;
    void LockCompositingSurface() override;
    void UnlockCompositingSurface() override;

    bool LockMouse() override;
    void UnlockMouse() override;
    void OnSetNeedsFlushInput() override;
    void GestureEventAck(const blink::WebGestureEvent& event,
        InputEventAckState ack_result) override;

    std::unique_ptr<SyntheticGestureTarget> CreateSyntheticGestureTarget()
        override;

    cc::FrameSinkId GetFrameSinkId() override;
    cc::FrameSinkId FrameSinkIdAtPoint(cc::SurfaceHittestDelegate* delegate,
        const gfx::Point& point,
        gfx::Point* transformed_point) override;
    // Returns true when we can do SurfaceHitTesting for the event type.
    bool ShouldRouteEvent(const blink::WebInputEvent& event) const;
    void ProcessMouseEvent(const blink::WebMouseEvent& event,
        const ui::LatencyInfo& latency) override;
    void ProcessMouseWheelEvent(const blink::WebMouseWheelEvent& event,
        const ui::LatencyInfo& latency) override;
    void ProcessTouchEvent(const blink::WebTouchEvent& event,
        const ui::LatencyInfo& latency) override;
    void ProcessGestureEvent(const blink::WebGestureEvent& event,
        const ui::LatencyInfo& latency) override;
    bool TransformPointToLocalCoordSpace(const gfx::Point& point,
        const cc::SurfaceId& original_surface,
        gfx::Point* transformed_point) override;
    bool TransformPointToCoordSpaceForView(
        const gfx::Point& point,
        RenderWidgetHostViewBase* target_view,
        gfx::Point* transformed_point) override;

    // TextInputManager::Observer implementation.
    void OnUpdateTextInputStateCalled(TextInputManager* text_input_manager,
        RenderWidgetHostViewBase* updated_view,
        bool did_update_state) override;
    void OnImeCancelComposition(TextInputManager* text_input_manager,
        RenderWidgetHostViewBase* updated_view) override;
    void OnImeCompositionRangeChanged(
        TextInputManager* text_input_manager,
        RenderWidgetHostViewBase* updated_view) override;
    void OnSelectionBoundsChanged(
        TextInputManager* text_input_manager,
        RenderWidgetHostViewBase* updated_view) override;
    void OnTextSelectionChanged(TextInputManager* text_input_manager,
        RenderWidgetHostViewBase* updated_view) override;
    // IPC::Sender implementation.
    bool Send(IPC::Message* message) override;

    // display::DisplayObserver implementation.
    void OnDisplayAdded(const display::Display& new_display) override;
    void OnDisplayRemoved(const display::Display& old_display) override;
    void OnDisplayMetricsChanged(const display::Display& display,
        uint32_t metrics) override;

    // Forwards the mouse event to the renderer.
    void ForwardMouseEvent(const blink::WebMouseEvent& event);

    void KillSelf();

    void SetTextInputActive(bool active);

    const std::string& selected_text() const { return selected_text_; }
    const gfx::Range& composition_range() const { return composition_range_; }
    const base::string16& selection_text() const { return selection_text_; }
    size_t selection_text_offset() const { return selection_text_offset_; }

    // Returns true and stores first rectangle for character range if the
    // requested |range| is already cached, otherwise returns false.
    // Exposed for testing.
    CONTENT_EXPORT bool GetCachedFirstRectForCharacterRange(
        NSRange range, NSRect* rect, NSRange* actual_range);

    // Returns true if there is line break in |range| and stores line breaking
    // point to |line_breaking_point|. The |line_break_point| is valid only if
    // this function returns true.
    bool GetLineBreakIndex(const std::vector<gfx::Rect>& bounds,
        const gfx::Range& range,
        size_t* line_break_point);

    // Returns composition character boundary rectangle. The |range| is
    // composition based range. Also stores |actual_range| which is corresponding
    // to actually used range for returned rectangle.
    gfx::Rect GetFirstRectForCompositionRange(const gfx::Range& range,
        gfx::Range* actual_range);

    // Converts from given whole character range to composition oriented range. If
    // the conversion failed, return gfx::Range::InvalidRange.
    gfx::Range ConvertCharacterRangeToCompositionRange(
        const gfx::Range& request_range);

    WebContents* GetWebContents();

    // These member variables should be private, but the associated ObjC class
    // needs access to them and can't be made a friend.

    // The associated Model.  Can be NULL if Destroy() is called when
    // someone (other than superview) has retained |cocoa_view_|.
    RenderWidgetHostImpl* render_widget_host_;

    // The background CoreAnimation layer which is hosted by |cocoa_view_|.
    base::scoped_nsobject<CALayer> background_layer_;

    // Delegated frame management and compositor interface.
    std::unique_ptr<BrowserCompositorMac> browser_compositor_;

    // Set when the currently-displayed frame is the minimum scale. Used to
    // determine if pinch gestures need to be thresholded.
    bool page_at_minimum_scale_;

    NSWindow* pepper_fullscreen_window() const
    {
        return pepper_fullscreen_window_;
    }

    CONTENT_EXPORT void release_pepper_fullscreen_window_for_testing();

    RenderWidgetHostViewMac* fullscreen_parent_host_view() const
    {
        return fullscreen_parent_host_view_;
    }

    int window_number() const;

    // The scale factor for the screen that the view is currently on.
    float ViewScaleFactor() const;

    // Update properties, such as the scale factor for the backing store
    // and for any CALayers, and the screen color profile.
    void UpdateBackingStoreProperties();

    // Ensure that the display link is associated with the correct display.
    void UpdateDisplayLink();

    void PauseForPendingResizeOrRepaintsAndDraw();

    // BrowserCompositorMacClient implementation.
    NSView* BrowserCompositorMacGetNSView() const override;
    SkColor BrowserCompositorMacGetGutterColor(SkColor color) const override;
    void BrowserCompositorMacSendReclaimCompositorResources(
        int compositor_frame_sink_id,
        bool is_swap_ack,
        const cc::ReturnedResourceArray& resources) override;
    void BrowserCompositorMacSendBeginFrame(
        const cc::BeginFrameArgs& args) override;

    // AcceleratedWidgetMacNSView implementation.
    NSView* AcceleratedWidgetGetNSView() const override;
    void AcceleratedWidgetGetVSyncParameters(
        base::TimeTicks* timebase, base::TimeDelta* interval) const override;
    void AcceleratedWidgetSwapCompleted() override;

    // Exposed for testing.
    cc::SurfaceId SurfaceIdForTesting() const override;

    // Helper method to obtain ui::TextInputType for the active widget from the
    // TextInputManager.
    ui::TextInputType GetTextInputType();

    // Helper method to obtain the currently active widget from TextInputManager.
    // An active widget is a RenderWidget which is currently focused and has a
    // |TextInputState.type| which is not ui::TEXT_INPUT_TYPE_NONE.
    RenderWidgetHostImpl* GetActiveWidget();

private:
    friend class RenderWidgetHostViewMacTest;

    // Returns whether this render view is a popup (autocomplete window).
    bool IsPopup() const;

    // Shuts down the render_widget_host_.  This is a separate function so we can
    // invoke it from the message loop.
    void ShutdownHost();

    // IPC message handlers.
    void OnGetRenderedTextCompleted(const std::string& text);

    // Send updated vsync parameters to the top level display.
    void UpdateDisplayVSyncParameters();

    // Dispatches a TTS session.
    void SpeakText(const std::string& text);

    // Get the focused view that should be used for retrieving the text selection.
    RenderWidgetHostViewBase* GetFocusedViewForTextSelection();

    // Adds/Removes frame observer based on state.
    void UpdateNeedsBeginFramesInternal();

    // The associated view. This is weak and is inserted into the view hierarchy
    // to own this RenderWidgetHostViewMac object. Set to nil at the start of the
    // destructor.
    RenderWidgetHostViewCocoa* cocoa_view_;

    // Indicates if the page is loading.
    bool is_loading_;

    // Whether it's allowed to pause waiting for a new frame.
    bool allow_pause_for_resize_or_repaint_;

    // The last scroll offset of the view.
    gfx::Vector2dF last_scroll_offset_;

    // The text to be shown in the tooltip, supplied by the renderer.
    base::string16 tooltip_text_;

    // True when this view acts as a platform view hack for a
    // RenderWidgetHostViewGuest.
    bool is_guest_view_hack_;

    // selected text on the renderer.
    std::string selected_text_;

    // The window used for popup widgets.
    base::scoped_nsobject<NSWindow> popup_window_;

    // The fullscreen window used for pepper flash.
    base::scoped_nsobject<NSWindow> pepper_fullscreen_window_;
    base::scoped_nsobject<FullscreenWindowManager> fullscreen_window_manager_;
    // Our parent host view, if this is fullscreen.  NULL otherwise.
    RenderWidgetHostViewMac* fullscreen_parent_host_view_;

    // Display link for getting vsync info.
    scoped_refptr<ui::DisplayLinkMac> display_link_;

    // The current VSync timebase and interval. This is zero until the first call
    // to SendVSyncParametersToRenderer(), and refreshed regularly thereafter.
    base::TimeTicks vsync_timebase_;
    base::TimeDelta vsync_interval_;

    // The current composition character range and its bounds.
    gfx::Range composition_range_;
    std::vector<gfx::Rect> composition_bounds_;

    // Whether a request for begin frames has been issued.
    bool needs_begin_frames_;

    // Whether a request to flush input has been issued.
    bool needs_flush_input_;

    // Factory used to safely scope delayed calls to ShutdownHost().
    base::WeakPtrFactory<RenderWidgetHostViewMac> weak_factory_;

    DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMac);
};

} // namespace content

#endif // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_MAC_H_
